package com.gengzp.system.service.impl;

import com.fasterxml.jackson.core.type.TypeReference;
import com.gengzp.common.enums.TaskExecuteStatusEnum;
import com.gengzp.common.enums.YesNoEnum;
import com.gengzp.common.exception.BusinessException;
import com.gengzp.common.model.generic.resp.Result;
import com.gengzp.common.model.pagination.resp.PaginationResponse;
import com.gengzp.common.model.task.entity.ScheduledTask;
import com.gengzp.common.model.task.req.CreateScheduledTaskReq;
import com.gengzp.common.model.task.req.EditScheduledTaskReq;
import com.gengzp.common.model.task.req.EditScheduledTaskStatusReq;
import com.gengzp.common.model.task.req.PageScheduledTaskReq;
import com.gengzp.common.model.task.resp.PageScheduledTaskResp;
import com.gengzp.common.model.task.resp.SearchScheduledTaskResp;
import com.gengzp.common.utils.*;
import com.gengzp.rpc.utils.RpcUtils;
import com.gengzp.system.mapper.ScheduledTaskMapper;
import com.gengzp.system.service.ScheduledTaskService;
import com.gengzp.system.utils.OperatorInfoUtils;
import com.gengzp.system.utils.ScheduledTaskUtils;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

import static com.gengzp.common.constants.HttpStatusCodeConstants.SUCCESS;
import static com.gengzp.common.constants.ScheduledTaskCacheConstants.SCHEDULED_TASK_EXECUTING_LOCK_PREFIX;

/**
 * @ClassName ScheduledTaskServiceImpl
 * @Description 定时任务管理模块服务层实现
 * @Author gengzp
 * @Date 2025/9/30 11:26
 */
@Service
public class ScheduledTaskServiceImpl implements ScheduledTaskService {

    private static final Logger logger = LoggerFactory.getLogger(ScheduledTaskServiceImpl.class);

    @Autowired
    private ScheduledTaskMapper scheduledTaskMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createScheduledTask(CreateScheduledTaskReq req) {
        // 先进行重复校验
        List<String> errorMessages = ThreadUtils.batchExecute(
                () -> {
                    if (Objects.nonNull(scheduledTaskMapper.selectScheduledTaskByTaskName(req.getTaskName()))) {
                        throw BusinessException.get(String.format("任务名称【%s】已存在", req.getTaskName()));
                    }
                },
                () -> {
                    if (Objects.nonNull(scheduledTaskMapper.selectScheduledTaskByUri(req.getUri()))) {
                        throw BusinessException.get(String.format("任务接口uri【%s】已存在", req.getUri()));
                    }
                }
        );
        if (CollectionUtils.isNotEmpty(errorMessages)) {
            throw BusinessException.get(String.join("； ", errorMessages) + "； 请勿创建重复的定时任务");
        }

        // 解析 Cron 表达式, 计算下次执行时间
        CronExpression cron = CronExpression.parse(req.getCronExpression());
        LocalDateTime nextLocalDateTime = null;
        try {
            nextLocalDateTime = cron.next(LocalDateTime.now());
        } catch (Exception e) {
            throw BusinessException.get(String.format("Cron表达式：【%s】解析失败", req.getCronExpression()));
        }
        if (Objects.isNull(nextLocalDateTime)) {
            throw BusinessException.get(String.format("Cron表达式：【%s】解析失败", req.getCronExpression()));
        }
        Date nextExecuteTime = Date.from(nextLocalDateTime.atZone(ZoneId.systemDefault()).toInstant());

        ScheduledTask scheduledTask = new ScheduledTask();
        scheduledTask.setTaskName(req.getTaskName());
        scheduledTask.setTaskDescription(req.getTaskDescription());
        scheduledTask.setServiceName(req.getServiceName());
        scheduledTask.setUri(req.getUri());
        scheduledTask.setLastExecuteTime(null);
        scheduledTask.setNextExecuteTime(nextExecuteTime);
        scheduledTask.setCronExpression(req.getCronExpression());
        // 将 cron 表达式转换为自然语言
        scheduledTask.setCronDescription(getCronDescription(req.getCronExpression()));
        scheduledTask.setLastExecuteStatus(TaskExecuteStatusEnum.UN_EXECUTED.getCode());
        scheduledTask.setLastExecuteResponse(null);
        scheduledTask.setStartStatus(YesNoEnum.NO.getCode());
        scheduledTask.setExecuteCount(0);
        scheduledTask.setIsDeleted(YesNoEnum.NO.getCode());
        scheduledTaskMapper.insertScheduledTask(scheduledTask);

        // 唤醒所有定时任务执行器
        notifyAllScheduledTaskExecutor();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void editScheduledTaskStatus(EditScheduledTaskStatusReq req) {
        ScheduledTask scheduledTask = scheduledTaskMapper.selectScheduledTaskById(Long.valueOf(req.getId()));
        if (Objects.isNull(scheduledTask)) {
            throw BusinessException.get("当前定时任务不存在");
        }
        if (Objects.equals(scheduledTask.getStartStatus(), req.getStatus())) {
            throw BusinessException.get(String.format("当前定时任务已处于【%s】状态, 无需重复操作",
                    YesNoEnum.getValueByCode(req.getStatus())));
        }

        scheduledTask.setStartStatus(req.getStatus());
        scheduledTaskMapper.updateScheduledTaskById(scheduledTask);

        // 唤醒所有定时任务执行器
        notifyAllScheduledTaskExecutor();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void editScheduledTask(EditScheduledTaskReq req) {
        ScheduledTask scheduledTask = scheduledTaskMapper.selectScheduledTaskById(Long.valueOf(req.getId()));
        if (Objects.isNull(scheduledTask)) {
            throw BusinessException.get("当前定时任务不存在");
        }
        if (Objects.equals(YesNoEnum.YES.getCode(), scheduledTask.getStartStatus())) {
            throw BusinessException.get("当前定时任务为开启状态，请先停止该任务再进行编辑");
        }

        // 任务防重
        List<String> errorMessages = ThreadUtils.batchExecute(() -> {
            ScheduledTask existTask = scheduledTaskMapper
                    .selectScheduledTaskByTaskName(req.getTaskName());
            if (Objects.nonNull(existTask) && !Objects.equals(existTask.getId(), scheduledTask.getId())) {
                throw BusinessException.get(String.format("任务名称【%s】已存在", req.getTaskName()));
            }
        }, () -> {
            ScheduledTask existTask = scheduledTaskMapper.selectScheduledTaskByUri(req.getUri());
            if (Objects.nonNull(existTask) && !Objects.equals(existTask.getId(), scheduledTask.getId())) {
                throw BusinessException.get(String.format("任务接口uri【%s】已存在", req.getUri()));
            }
        });
        if (CollectionUtils.isNotEmpty(errorMessages)) {
            throw BusinessException.get(String.join("； ", errorMessages) + "； 请勿创建重复的定时任务");
        }

        // 解析 Cron 表达式, 计算下次执行时间
        CronExpression cron = CronExpression.parse(req.getCronExpression());
        LocalDateTime nextLocalDateTime = null;
        try {
            nextLocalDateTime = cron.next(LocalDateTime.now());
        } catch (Exception e) {
            throw BusinessException.get(String.format("Cron表达式【%s】解析失败", req.getCronExpression()));
        }
        assert nextLocalDateTime != null;
        Date nextExecuteTime = Date.from(nextLocalDateTime.atZone(ZoneId.systemDefault()).toInstant());

        scheduledTask.setTaskName(req.getTaskName());
        scheduledTask.setTaskDescription(req.getTaskDescription());
        scheduledTask.setServiceName(req.getServiceName());
        scheduledTask.setUri(req.getUri());
        scheduledTask.setNextExecuteTime(nextExecuteTime);
        scheduledTask.setCronExpression(req.getCronExpression());
        // 将 cron 表达式转换为自然语言
        scheduledTask.setCronDescription(getCronDescription(req.getCronExpression()));
        scheduledTaskMapper.updateScheduledTaskById(scheduledTask);

        // 唤醒所有定时任务执行器
        notifyAllScheduledTaskExecutor();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeScheduledTaskById(Long taskId) {
        ScheduledTask scheduledTask = scheduledTaskMapper.selectScheduledTaskById(taskId);
        if (Objects.isNull(scheduledTask)) {
            throw BusinessException.get("当前定时任务不存在");
        }
        if (Objects.equals(YesNoEnum.YES.getCode(), scheduledTask.getStartStatus())) {
            throw BusinessException.get("当前定时任务为开启状态，请先关闭定时任务再进行删除操作");
        }

        scheduledTaskMapper.deleteScheduledTaskById(taskId);

        // 唤醒所有定时任务执行器
        notifyAllScheduledTaskExecutor();
    }

    @Override
    public SearchScheduledTaskResp searchScheduledTaskById(Long taskId) {
        ScheduledTask scheduledTask = scheduledTaskMapper.selectScheduledTaskById(taskId);
        if (Objects.isNull(scheduledTask)) {
            throw BusinessException.get("当前定时任务不存在");
        }

        SearchScheduledTaskResp resp = new SearchScheduledTaskResp();
        resp.setId(String.valueOf(taskId));
        resp.setTaskName(scheduledTask.getTaskName());
        resp.setTaskDescription(scheduledTask.getTaskDescription());
        resp.setServiceName(scheduledTask.getServiceName());
        resp.setUri(scheduledTask.getUri());
        resp.setLastExecuteTime(scheduledTask.getLastExecuteTime());
        resp.setNextExecuteTime(scheduledTask.getNextExecuteTime());
        resp.setCronExpression(scheduledTask.getCronExpression());
        resp.setCronDescription(scheduledTask.getCronDescription());
        resp.setLastExecuteStatus(scheduledTask.getLastExecuteStatus());
        resp.setLastExecuteStatusValue(
                TaskExecuteStatusEnum.getValueByCode(scheduledTask.getLastExecuteStatus()));
        resp.setLastExecuteResponse(scheduledTask.getLastExecuteResponse());
        resp.setStartStatus(scheduledTask.getStartStatus());
        resp.setStartStatusValue(
                YesNoEnum.getValueByCode(scheduledTask.getStartStatus()));
        resp.setExecuteCount(scheduledTask.getExecuteCount());
        resp.setCreatedBy(String.valueOf(scheduledTask.getCreatedBy()));
        resp.setCreatedTime(scheduledTask.getCreatedTime());
        resp.setLastModifiedBy(String.valueOf(scheduledTask.getLastModifiedBy()));
        resp.setLastModifiedTime(scheduledTask.getLastModifiedTime());
        // 填充操作人信息
        OperatorInfoUtils.fillInGenericRespOperatorInformation(resp);

        return resp;
    }

    @Override
    public PaginationResponse<PageScheduledTaskResp> pageQueryScheduledTask(PageScheduledTaskReq req) {
        List<ScheduledTask> scheduledTasks = scheduledTaskMapper.pageQueryScheduledTask(req);
        if (CollectionUtils.isEmpty(scheduledTasks)) {
            return PaginationResponse.get(Lists.newArrayList(), req);
        }

        List<PageScheduledTaskResp> result = Lists.newArrayList();
        for (ScheduledTask task : scheduledTasks) {
            PageScheduledTaskResp resp = new PageScheduledTaskResp();
            resp.setId(String.valueOf(task.getId()));
            resp.setTaskName(task.getTaskName());
            resp.setTaskDescription(task.getTaskDescription());
            resp.setServiceName(task.getServiceName());
            resp.setUri(task.getUri());
            resp.setLastExecuteTime(task.getLastExecuteTime());
            resp.setNextExecuteTime(task.getNextExecuteTime());
            resp.setCronDescription(task.getCronDescription());

            // 如果分布式锁存在, 表示正在执行中
            if (Objects.nonNull(RedisUtils.get(SCHEDULED_TASK_EXECUTING_LOCK_PREFIX + task.getId()))) {
                resp.setLastExecuteStatus(TaskExecuteStatusEnum.EXECUTING.getCode());
            } else {
                resp.setLastExecuteStatus(task.getLastExecuteStatus());
            }

            resp.setLastExecuteStatusValue(
                    TaskExecuteStatusEnum.getValueByCode(task.getLastExecuteStatus()));
            resp.setStartStatus(task.getStartStatus());
            resp.setStartStatusValue(
                    YesNoEnum.getValueByCode(task.getStartStatus()));
            resp.setExecuteCount(task.getExecuteCount());
            resp.setCreatedBy(String.valueOf(task.getCreatedBy()));
            resp.setCreatedTime(task.getCreatedTime());
            resp.setLastModifiedBy(String.valueOf(task.getLastModifiedBy()));
            resp.setLastModifiedTime(task.getLastModifiedTime());
            result.add(resp);
        }
        // 填充操作人信息
        OperatorInfoUtils.fillInGenericRespOperatorInformation(result);

        return PaginationResponse.get(result, req);
    }

    @Override
    public void doExecuteScheduledTask(Long taskId) {
        ScheduledTask task = scheduledTaskMapper.selectScheduledTaskById(taskId);
        if (Objects.isNull(task)) {
            throw BusinessException.get("当前定时任务id不存在");
        }

        ThreadUtils.execute(() -> {
            // 手动触发定时任务需要通过分布式锁进行管理
            DistributedLockUtils.doExecuteWithLock(() -> {
                Date currentDate = new Date();
                Result<Void> result = null;
                String errorMessage = null;
                String traceId = UUID.randomUUID().toString().replace("-", "");
                try {
                    result = HttpRequestUtils.post(
                            "http://" + RpcUtils.getAddressByServiceName(task.getServiceName()) + task.getUri(),
                            null,
                            Map.of("traceId", traceId),
                            new TypeReference<>() {
                            });
                } catch (Exception e) {
                    errorMessage = e.getMessage();
                }
                if (Objects.isNull(result)) {
                    result = Result.getFailResult(errorMessage);
                }
                // 为返回结果拼装链路追踪id
                result.setTraceId(traceId);

                task.setLastExecuteResponse(JsonUtils.toJsonString(result));
                task.setLastExecuteTime(currentDate);
                if (Objects.equals(result.getCode(), SUCCESS)) {
                    task.setLastExecuteStatus(TaskExecuteStatusEnum.EXECUTE_SUCCESS.getCode());
                } else {
                    task.setLastExecuteStatus(TaskExecuteStatusEnum.EXECUTE_FAILED.getCode());
                }
                scheduledTaskMapper.updateScheduledTaskAfterExecute(task);
            }, SCHEDULED_TASK_EXECUTING_LOCK_PREFIX + taskId);
        });
    }

    @Override
    public void notifyScheduledTaskExecutor() {
        ScheduledTaskUtils.notifyScheduledTaskExecutor();
    }

    /**
     * 唤醒所有定时任务执行器
     */
    private void notifyAllScheduledTaskExecutor() {
        // 获取所有系统服务的在线实例
        List<String> SystemServiceAddresses = RpcUtils.getAllAddressesByServiceName("system");
        if (CollectionUtils.isEmpty(SystemServiceAddresses)) {
            return;
        }

        // 同时唤醒所有在现实里的定时任务执行器, 全部参与锁竞争
        logger.info("开始唤醒所有定时任务执行器, 共【{}】个", SystemServiceAddresses.size());
        int index = 1;
        for (String address : SystemServiceAddresses) {
            try {
                HttpRequestUtils.post("http://" + address + "/task/notify_executor",
                        null,
                        // 将当前请求的 traceId 携带下去
                        Map.of("traceId", TraceUtils.getCurrentTraceId()));
                logger.info("唤醒【{}】定时任务执行器成功 【{}/{}】",
                        address, index, SystemServiceAddresses.size());
            } catch (Exception e) {
                logger.info("唤醒【{}】定时任务执行器失败 【{}/{}】, {}",
                        address, index, SystemServiceAddresses.size(), e.getMessage());
            }
            index++;
        }
    }

    /**
     * 获取 cron 表达式的描述
     * 该方法会将cron表达式转换为自然语言
     *
     * @param cronExpression cron表达式
     * @return 描述cron的自然语言
     */
    private String getCronDescription(String cronExpression) {
        if (StringUtils.isBlank(cronExpression)) {
            return "";
        }

        // 用空格分隔开, 根据下标读取 秒 分 时 日 月 周
        String[] split = cronExpression.split(" ");
        List<String> descriptions = Lists.newArrayList();
        String week = split[5];
        String month = split[4];
        String day = split[3];
        String hour = split[2];
        String minute = split[1];
        String second = split[0];

        if (!Objects.equals(week, "?") && !Objects.equals(week, "*")) {
            descriptions.add(convertCronSymbol(week, "周"));
        } else {
            descriptions.add("");
        }
        descriptions.add(convertCronSymbol(month, "个月"));
        descriptions.add(convertCronSymbol(day, "天"));
        descriptions.add(convertCronSymbol(hour, "小时"));
        descriptions.add(convertCronSymbol(minute, "分钟"));
        descriptions.add(convertCronSymbol(second, "秒"));

        // 可以省略的描述部分
        List<String> removeDescriptionPart = Lists.newArrayList();
        for (int i = 0; i <= 4; i++) { //忽略周, 因此i只到4
            // 当前cron符号
            String currentSymbol = split[4 - i];
            // 下一级cron符号
            String nextSymbol = split[3 - i];

            // 如果连续两层都是 "*", 那么前一个可以忽略
            if (Objects.equals(currentSymbol, "*") &&
                    Objects.equals(nextSymbol, "*")) {
                removeDescriptionPart.add(descriptions.get(i + 1));
            } else {
                break;
            }
        }
        if (CollectionUtils.isNotEmpty(removeDescriptionPart)) {
            descriptions.removeAll(removeDescriptionPart);
        }

        return descriptions.stream().filter(StringUtils::isNotBlank)
                .collect(Collectors.joining("的")) + "执行一次";
    }

    /**
     * 将 cron 表达式中的符号转换为自然语言
     *
     * @param symbol   cron 表达式中的符号
     * @param unitName 时间单位
     * @return 对应单位转换后的自然语言
     */
    private String convertCronSymbol(String symbol, String unitName) {
        if (Objects.equals(symbol, "?")) {
            return "";
        }

        if (Objects.equals(symbol, "*") || Objects.equals(symbol, "*/1")) {
            return "每" + unitName;
        }

        if (symbol.startsWith("*/")) {
            return "每" + symbol.substring(2) + unitName;
        }

        return "第" + symbol + unitName;
    }


}
